/* ROM-Cache-MEM2.c - ROM Cache utilizing MEM2 on Wii
   by Mike Slegeir for Mupen64-GC
 ******************************************************
   Optimization: Store whatever blocks that don't fit
                   in MEM2 cache on the filesystem
                   under /tmp; keep an extra block for
                   asynchronous write-back
                 Create a L1 cache in MEM1
 */

#include <stdio.h>
#include "ROM-Cache.h"
#include "../gc_memory/MEM2.h"
#include "../fileBrowser/fileBrowser.h"

#ifdef USE_GUI
#include "../gui/GUI.h"
#include "../gui/gui_GX-menu.h"
#define PRINT GUI_print
#else
#define PRINT printf
#endif
#include "../gui/DEBUG.h"

#define BLOCK_SIZE (1024*1024)
#define LOAD_SIZE  (4*1024)
static u32   ROMSize;
static int   ROMTooBig;
extern int   ROM_byte_swap;
static char* ROMBlocks[64];
static int   ROMBlocksLRU[64];
static fileBrowser_file* ROMFile;
extern int hasLoadedROM;
extern int stop;
void* memcpy(void* dst, void* src, int len);
void showLoadProgress(float);

static void ensure_block(u32 block);

void ROMCache_init(u32 size){
	ROMSize = size;
	ROMTooBig = size > ROMCACHE_SIZE;
	
	//romFile_init( romFile_topLevel );
}

void ROMCache_deinit(){
	//romFile_deinit( romFile_topLevel );
}

void* ROMCache_pointer(u32 rom_offset){
	if(ROMTooBig){
		u32 block = rom_offset >> 20;
		u32 block_offset = rom_offset & 0xFFFFF;
		
		ensure_block(block);
		
		return ROMBlocks[block] + block_offset;
	} else {
		return ROMCACHE_LO + rom_offset;
	}
}

void ROMCache_load_block(char* dst, u32 rom_offset){
  if((hasLoadedROM) && (!stop))
    pauseAudio();
	romFile_seekFile(ROMFile, rom_offset, FILE_BROWSER_SEEK_SET);
	
	u32 offset = 0, bytes_read, loads_til_update = 0;
	while(offset < BLOCK_SIZE){
		bytes_read = romFile_readFile(ROMFile, dst + offset, LOAD_SIZE);
		byte_swap(dst + offset, bytes_read);
		offset += bytes_read;
		
		if(!loads_til_update--){
			showLoadProgress( (float)offset/BLOCK_SIZE );
			loads_til_update = 32;
		}
	}
	showLoadProgress( 1.0f );
	if((hasLoadedROM) && (!stop))
	  resumeAudio();
}

static void ensure_block(u32 block){
	if(!ROMBlocks[block]){
		// The block we're trying to read isn't in the cache
		// Find the Least Recently Used Block
		int i, max_i = 0, max_lru = 0;
		for(i=0; i<64; ++i)
			if(ROMBlocks[i] && ROMBlocksLRU[i] > max_lru)
				max_i = i, max_lru = ROMBlocksLRU[i];
		ROMBlocks[block] = ROMBlocks[max_i]; // Take its place
		ROMCache_load_block(ROMBlocks[block], (block<<20)&0xFFF00000);
		ROMBlocks[max_i] = 0; // Evict the LRU block
	}
}

void ROMCache_read(u32* dest, u32 offset, u32 length){
	// Display stats for reads
	static int last_block = -1;
	if( offset>>18 == last_block )
		DEBUG_stats(3, "ROMCache same block", STAT_TYPE_ACCUM, 1);
	else
		DEBUG_stats(4, "ROMCache different block", STAT_TYPE_ACCUM, 1);
	last_block = offset >> 18;
	DEBUG_stats(5, "ROMCache avg length", STAT_TYPE_AVGE, length);
	
	if(ROMTooBig){
		u32 block = offset>>20;
		u32 length2 = length;
		u32 offset2 = offset&0xFFFFF;
		
		while(length2){
			ensure_block(block);
			
			// Set length to the length for this block
			if(length2 > BLOCK_SIZE - offset2)
				length = BLOCK_SIZE - offset2;
			else length = length2;
		
			// Increment LRU's; set this one to 0
			int i;
			for(i=0; i<64; ++i) ++ROMBlocksLRU[i];
			ROMBlocksLRU[block] = 0;
			
			// Actually read for this block
			memcpy(dest, ROMBlocks[block] + offset2, length);
			
			// In case the read spans multiple blocks, increment state
			++block; length2 -= length; offset2 = 0; dest += length/4; offset += length;
		}
	} else {
		memcpy(dest, ROMCACHE_LO + offset, length);
	}
}

void ROMCache_load(fileBrowser_file* f, int byteSwap){
	char txt[128];
	GUI_clear();
	GUI_centerText(true);
	sprintf(txt, "Loading ROM %s into MEM2.\n Please be patient...\n", ROMTooBig ? "partially" : "fully");
	PRINT(txt);
	
	ROM_byte_swap = byteSwap;
	ROMFile = f;
	romFile_seekFile(f, 0, FILE_BROWSER_SEEK_SET);
	
	u32 offset = 0, bytes_read, loads_til_update = 0;
	u32 sizeToLoad = MIN(ROMCACHE_SIZE, ROMSize);
	while(offset < sizeToLoad){
		bytes_read = romFile_readFile(f, ROMCACHE_LO + offset, LOAD_SIZE);
		byte_swap(ROMCACHE_LO + offset, bytes_read);
		offset += bytes_read;
		
		if(!loads_til_update--){
			GUI_setLoadProg( (float)offset/sizeToLoad );
			GUI_draw();
			loads_til_update = 16;
		}
	}
	
	if(ROMTooBig){ // Set block pointers if we need to
		int i;
		for(i=0; i<ROMCACHE_SIZE/BLOCK_SIZE; ++i)
			ROMBlocks[i] = ROMCACHE_LO + i*BLOCK_SIZE;
		for(; i<ROMSize/BLOCK_SIZE; ++i)
			ROMBlocks[i] = 0;
		for(i=0; i<ROMSize/BLOCK_SIZE; ++i)
			ROMBlocksLRU[i] = i;
	}
	
	GUI_setLoadProg( -1.0f );
}



